﻿/*************************************************************************  
   Name: PluginsManager.cs 
   Author: Timothy Ye 
   BLOG: http://www.xiaozhou.net/cooldog
   Version: 1.0
   Last Modify Time:
   Modify Reason:
**************************************************************************/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
using System.IO;
using System.Xml.Linq;

using Xiaozhou.Utility;

namespace Xiaozhou.Plugins
{
    public enum PluginEventType
    {
        PluginLoadOver,
        PluginAdded,
        PluginRemoved,
        PluginStopped,
        PluginStarted
    }

    public class PluginEventArgs : EventArgs
    {
        #region Private Members
        public PluginEventType EventType { get; set; }
        public string EventMessage { get; set; }
        #endregion

        public PluginEventArgs(PluginEventType eventtype, string message)
        {
            EventType = eventtype;
            EventMessage = message;
        }

    }

    public class PluginsManager
    {
        private Dictionary<string, object> pluginInstanceContainer;
        private AssemblyLoader loader;
        private static PluginsManager _instance;
        private FileSystemWatcher filewatcher = null;
        private string pluginpath = string.Empty;

        public delegate void PluginEventHandler(PluginEventArgs args);
        public event PluginEventHandler pluginsManagerEvent;

        public static PluginsManager GetInstance()
        {
            if (_instance == null)
            {
                _instance = new PluginsManager();
            }

            return _instance;
        }

        private PluginsManager()
        {
            pluginInstanceContainer = new Dictionary<string, object>();
            loader = new AssemblyLoader();

            if (string.IsNullOrEmpty(ConfigurationManager.AppSettings["PluginsPath"]))
            {
                Logger.Error("配置文件中找不到PluginsPath配置项");
                throw new Exception("Can not find Plugins Path definition or PluginsPath is NULL!");
            }
            else
            {
                pluginpath = ConfigurationManager.AppSettings["PluginsPath"];
            }

        }

        public void Run()
        {
            if (pluginsManagerEvent != null)
            {
                pluginsManagerEvent(new PluginEventArgs(PluginEventType.PluginStarted, "Plugin started!"));
                Logger.Info("Plugin Manager 已启动...");
            }

            CompileAllAssembly();
            LoadAllAssembly();
            StartMonitor();

            if (pluginsManagerEvent != null)
            {
                pluginsManagerEvent(new PluginEventArgs(PluginEventType.PluginLoadOver, "Plugin load over!"));
                Logger.Info("Plugin Manager 启动完毕...");
            }
        }

        private void StartMonitor()
        {
            filewatcher = new FileSystemWatcher(pluginpath);
            filewatcher.Filter = "*.xml";
            filewatcher.EnableRaisingEvents = true;
            filewatcher.IncludeSubdirectories = true;
            filewatcher.Changed += new FileSystemEventHandler(filewatcher_Changed);
            filewatcher.Created += new FileSystemEventHandler(filewatcher_Changed);

        }

        private void StopMonitor()
        {
            filewatcher.EnableRaisingEvents = false;
        }

        private void filewatcher_Changed(object obj, FileSystemEventArgs args)
        {
            switch (args.ChangeType)
            {
                case WatcherChangeTypes.Changed:
                    plugin_Changed(obj, args);
                    break;
                case WatcherChangeTypes.Created:
                    plugin_Created(obj, args);
                    break;
            }
        }

        private void plugin_Created(object obj, FileSystemEventArgs args)
        {
            System.Threading.Thread.Sleep(2000);

            XElement data = XElement.Load(args.FullPath);
            string key = Common.getFileName(args.FullPath);
            string assemblyFile = Common.getAssemblyFileName(args.FullPath);

            if (File.Exists(assemblyFile))
            {
                AppDomain pluginDomain = null;
                MarshalByRefObject instance = loader.LoadAssembly(assemblyFile,
                                                                  ref pluginDomain);

                if (instance != null)
                {
                    PluginEntity entity = new PluginEntity(key, instance, assemblyFile, pluginDomain);
                    pluginInstanceContainer.Add(key, entity);
                }

                if (pluginsManagerEvent != null)
                {
                    pluginsManagerEvent(new PluginEventArgs(PluginEventType.PluginAdded, key));
                    Logger.Info("插件:" + key + "已经被加载.");
                }
            }

        }

        private void plugin_Changed(object obj, FileSystemEventArgs args)
        {

            XElement data = XElement.Load(args.FullPath);
            string key = Common.getFileName(args.FullPath);
            string assemblyFile = Common.getAssemblyFileName(args.FullPath);

            if (bool.Parse(data.Element("EnablePlugin").Value))
            {
                if (!pluginInstanceContainer.Keys.Contains(key))
                {
                    if (File.Exists(assemblyFile))
                    {
                        AppDomain pluginDomain = null;
                        MarshalByRefObject instance = loader.LoadAssembly(assemblyFile,
                                                                          ref pluginDomain);

                        if (instance != null)
                        {
                            PluginEntity entity = new PluginEntity(key, instance, assemblyFile, pluginDomain);
                            pluginInstanceContainer.Add(key, entity);
                        }

                        if (pluginsManagerEvent != null)
                        {
                            pluginsManagerEvent(new PluginEventArgs(PluginEventType.PluginAdded, key));
                            Logger.Info("插件:" + key + "已经被加载.");
                        }
                    }

                }
            }
            else
            {
                if (pluginInstanceContainer.Keys.Contains(key))
                {
                    AppDomain pluginDomain = (pluginInstanceContainer[key] as PluginEntity).PluginDomain;
                    if (pluginDomain != null)
                    {
                        AppDomain.Unload(pluginDomain);
                    }

                    pluginInstanceContainer.Remove(key);

                    if (pluginsManagerEvent != null)
                    {
                        pluginsManagerEvent(new PluginEventArgs(PluginEventType.PluginRemoved, key));
                        Logger.Info("插件:" + key + "已经被卸载.");
                    }
                }
            }
        }

        public void Stop()
        {
            StopMonitor();
            UnloadAllAssembly();

            if (pluginsManagerEvent != null)
            {
                pluginsManagerEvent(new PluginEventArgs(PluginEventType.PluginStopped, "Plugin stopped!"));
                Logger.Info("Plugin Manager Stopped...");
            }

        }

        private void CompileAllAssembly()
        {
            Logger.Info("正在查找所有可编译的插件源码");
            string[] codeFileArr = Directory.GetFiles(pluginpath,
                                                      "*.cs",
                                                      SearchOption.AllDirectories);
            Logger.Info("找到" + codeFileArr.Length + "个需要编译的插件源码.");

            foreach (string s in codeFileArr)
            {
                string filename = Common.getFileName(s);
                string configfile = Common.getConfigFileName(s);
                if (File.Exists(configfile))
                {
                    if (Common.ExistsReferenceList(configfile))
                    {
                        XElement data = XElement.Load(configfile);
                        var param = data.Element("ReferenceList").Elements("Reference")
                                        .Select(element => element.Value);

                        List<string> paramList = new List<string>();

                        foreach (var t in param)
                        {
                            paramList.Add(t);
                        }

                        AssemblyFactory factory = new AssemblyFactory();
                        if (factory.CreateAssembly(s, paramList))
                        {
                            Logger.Info("插件" + filename + "编译成功.");
                        }
                        else
                        {
                            Logger.Info("插件" + filename + "编译失败.");
                        }
                    }
                    else
                    {
                        AssemblyFactory factory = new AssemblyFactory();
                        if (factory.CreateAssembly(s))
                        {
                            Logger.Info("插件" + filename + "编译成功.");
                        }
                        else
                        {
                            Logger.Info("插件" + filename + "编译失败.");
                        }
                    }
                }
            }
        }

        private void LoadAllAssembly()
        {
            Logger.Info("正在查找所有可加载的插件");
            string[] assemblyArr = Directory.GetFiles(pluginpath,
                                                      "*.dll",
                                                      SearchOption.AllDirectories);

            Logger.Info("找到" + assemblyArr.Length + "个需要加载的插件.");

            foreach (string strFile in assemblyArr)
            {
                AppDomain pluginDomain = null;
                MarshalByRefObject instance = loader.LoadAssembly(strFile, ref pluginDomain);

                if (instance != null)
                {
                    PluginEntity obj = new PluginEntity(Common.getFileName(strFile), instance, strFile, pluginDomain);
                    pluginInstanceContainer.Add(Common.getFileName(strFile), obj);
                    Logger.Info("插件: " + Common.getFileName(strFile) + " 已加载.");
                }
            }
        }

        private void UnloadAllAssembly()
        {
            Logger.Info("正在卸载所有的插件");
            foreach (string key in pluginInstanceContainer.Keys)
            {
                AppDomain pluginDomain = (pluginInstanceContainer[key] as PluginEntity).PluginDomain;
                if (pluginDomain != null)
                {
                    AppDomain.Unload(pluginDomain);
                }
            }

            pluginInstanceContainer.Clear();
            Logger.Info("所有插件卸载完毕");

        }

        public string[] GetPluginNames()
        {
            var arr = from p in pluginInstanceContainer.Keys
                      select p;
            return arr.ToArray<string>();
        }

        public MarshalByRefObject GetPluginByName(string name)
        {
            PluginEntity obj = pluginInstanceContainer[name] as PluginEntity;
            if (obj != null)
            {
                return obj.PluginInstance;
            }
            return null;
        }

        public bool UnloadPluginByName(string name)
        {
            PluginEntity obj = pluginInstanceContainer[name] as PluginEntity;
            if (obj != null)
            {
                AppDomain.Unload(obj.PluginDomain);
                pluginInstanceContainer.Remove(name);

                if (pluginsManagerEvent != null)
                {
                    pluginsManagerEvent(new PluginEventArgs(PluginEventType.PluginRemoved, name));
                }

                return true;
            }
            return false;
        }

        public AssemblyLoader AssemblyLoader
        {
            get
            {
                throw new System.NotImplementedException();
            }
            set
            {
            }
        }

        public AssemblyFactory AssemblyFactory
        {
            get
            {
                throw new System.NotImplementedException();
            }
            set
            {
            }
        }

        internal PluginEntity PluginEntity
        {
            get
            {
                throw new System.NotImplementedException();
            }
            set
            {
            }
        }

    }
}
